package com.ruoyi.core.lock;

import com.google.common.collect.ImmutableList;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.core.lock.config.MatchConst;
import com.ruoyi.core.lock.enums.LockType;
import com.ruoyi.framework.utils.SessionHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author xiaom
 * @Date 2020/3/13 13:47
 * @Version 1.0.0
 * @Description <>
 **/
@Aspect
@Component
@Slf4j
public class LockAspect {

    private static ThreadLocal<Lock> lockCache = new ThreadLocal<>();
    private static ThreadLocal<LockBasisInfo> lockConfig = new ThreadLocal<>();
    /**
     * Method pointcut.
     */
    @Pointcut("@annotation(com.ruoyi.core.lock.ULock)")
    public void pointCut() {}


    @After("pointCut()")
    public void unLock(JoinPoint point){
        unFunctionLock();
        setFrequency();
    }

    private void unFunctionLock(){
        if(lockCache.get() != null){
            lockCache.get().unLock();
            lockCache.remove();
        }
    }

    private void setFrequency(){
        if(lockConfig.get() == null){
            return;
        }
        //Fetch lock annotation.
        ULock uLock = lockConfig.get().getULock();
        //Check whether you need to judge the frequency
        if(uLock.interval() == -1){
            return;
        }
        String key = lockConfig.get().getKey();
        log.info("Set Function Interval. Key:[{}] Interval:[{}]. ",key,uLock.interval());
        RedissonUtil.getInstance().set(key,"pool",uLock.interval());
        lockConfig.remove();
    }

    @Before("pointCut()")
    public void lockCheck(JoinPoint joinPoint){
        //Fetch lock annotation.
        ULock uLock = getLockAnnotation(joinPoint);
        String key = getLockKey(joinPoint,uLock);

        //Check if request limit is required
        if(uLock.interval() > 0){
            Long ttl = RedissonUtil.getInstance().ttl(key);
            if(ttl > 0){
                log.error("Frequent visits, please try again in [{}] millisecond",ttl);
                throw new CustomException("操作频繁,请稍后再试");
            }
        }

        //Fetch Lock.
        lockCache.set(getLock(getLockAnnotation(joinPoint)));
        lockCache.get().lock(key);
        //Set Lock Basis Info
        lockConfig.set(new LockBasisInfo(key,uLock));
    }

    private Lock getLock(ULock lock){
        return new Lock(lock.maxTimeout(),lock.awaitTimeout());
    }

    private String getLockKey(JoinPoint joinPoint, ULock uLock){
        try {
            List<HashMap<String, LockType>> expressionList = getAllExpressions(uLock);

            LockModel lockModel = getProxyModel(joinPoint);
            lockModel.setULock(uLock);

            //Analyze annotation parameters dynamically
            List<String> realValueList = new ArrayList<>();

            expressionList.forEach(item -> {
                LockType lockType = item.values().iterator().next();
                String lockKey = item.keySet().iterator().next();
                lockModel.setExpression(parseLockAnnotationKey(lockKey,lockType));
                //Get lock real value.
                Collection<String> lockPrefix = lockType.getActuator().doExecute(lockModel);
                log.info("Lock actual value:{} Lock type:[{}]",lockPrefix,lockType.name());
                realValueList.addAll(lockPrefix);
            });

            return IExpressionActuator.assembly(
                    realValueList,
                    SecurityUtils.getLoginUser().getUser().getUserId() + "",
                    uLock.lockIp() ? getIp(SessionHelper.getContextRequest()) : "",
                    uLock.useMethodName() ? lockModel.getProxyMethod().getName() : ""
                );
        } catch (Exception e) {
            log.error("Lock before throw error. Message:[{}]",e.getMessage());
            throw e;
        }
    }

    public static String getIp(HttpServletRequest request) {
        String forwards = request.getHeader("x-forwarded-for");
        if (StringUtils.isBlank(forwards) || "unknown".equalsIgnoreCase(forwards)) {
            forwards = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(forwards) || "unknown".equalsIgnoreCase(forwards)) {
            forwards = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(forwards) || "unknown".equalsIgnoreCase(forwards)) {
            forwards = request.getRemoteAddr();
        }
        if (StringUtils.isBlank(forwards) || "unknown".equalsIgnoreCase(forwards)) {
            forwards = request.getHeader("X-Real-IP");
        }
        if (forwards != null && forwards.trim().length() > 0) {
            int index = forwards.indexOf(',');
            return (index != -1) ? forwards.substring(0, index) : forwards;
        }
        return forwards;
    }

    private ULock getLockAnnotation(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        //Fetch lock annotation.
        return targetMethod.getAnnotation(ULock.class);
    }

    /**
     * Parser all expressions
     */
    private List<HashMap<String,LockType>> getAllExpressions(ULock uLock){
        String lockValue = uLock.lockKey();
        List<HashMap<String,LockType>> expressionList = new ArrayList<>();

        HashMap<String,LockType> expressionMap;

        //Use Default Mode
        if(lockValue.isEmpty()){
            expressionMap = new HashMap<>();
            expressionMap.put("",LockType.DEFAULT_TYPE);
            return ImmutableList.of(expressionMap);
        }

        //Split. Fetch All Expression.
        String[] expressions = lockValue.split(",");
        for (String expression : expressions) {
            expressionMap = new HashMap<>();
            expressionMap.put(expression,parserType(expression));
            expressionList.add(expressionMap);
        }

        return expressionList;
    }


    /**
     * Judge type.
     */
    private LockType parserType(String expression){
        
        if(stringExtract(expression, MatchConst._PATTERN_M) != null){
            return LockType.EXPRESSION_TYPE;
        }
        if(stringExtract(expression, MatchConst._PATTERN_$) != null){
            return LockType.EXPRESSION_PARAM_TYPE;
        }
        return LockType.EXPRESSION_ORIGINAL_TYPE;
    }


    /**
     * 提取#{****}内的值
     */
    public static String stringExtract(String str, String pattern){
        Assert.notNull(pattern,"Extract pattern must not be null.");
        Pattern regex = Pattern.compile(pattern);
        Matcher matcher = regex.matcher(str);
        while(matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }

    /**
     * Parsing expressions in annotations
     * @param lockKey annotation
     * @return expressions
     */
    private List<String> parseLockAnnotationKey(String lockKey, LockType lockType) {
        switch (lockType){
            case EXPRESSION_TYPE:
                return parserExpressionKeyByPattern(lockKey, MatchConst._PATTERN_M);
            case EXPRESSION_PARAM_TYPE:
                return parserExpressionKeyByPattern(lockKey, MatchConst._PATTERN_$);
            case EXPRESSION_ORIGINAL_TYPE:
                return ImmutableList.of(lockKey);
            default:
                return new ArrayList<>();
        }
    }

    private List<String> parserExpressionKeyByPattern(String lockExpression, String pattern){
        //Params lock value expression
        String[] values = lockExpression.split(",");

        List<String> valList = new ArrayList<>();

        for (String value : values) {
            if(pattern == null){
                valList.add(value);
                continue;
            }
            String val = stringExtract(value,pattern);
            if(val == null) continue;
            valList.add(val);
        }
        return valList;
    }

    /**
     * Get proxy method args.
     */
    private LockModel getProxyModel(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        return new LockModel(joinPoint.getArgs(),null,methodSignature.getParameterNames(),methodSignature.getMethod());
    }
}
